﻿//Copyright 2008 Kristian Gundry And Rune Juhl-Petersen
//This file is part of Plain.

//Plain is free software: you can redistribute it and/or modify
//it under the terms of the GNU Lesser General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//any later version.

//Plain is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU Lesser General Public License for more details.

//You should have received a copy of the GNU Lesser General Public License
//along with Plain.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Linq;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Web.UI;
using System.Web.UI.WebControls;

using log4net;
using Plain;
using Plain.Data;
using Plain.Utilities.Reflection;
using Plain.Utilities;
using Plain.Web.Utilities;
using Plain.Web.Utilities.FormBinders;
using System.Web;


namespace Plain.Web {

    #region PlainEntityEventArgs

    public class PlainEntityManagerEventArgs<T> : EventArgs where T : new() {
        private T value;

        public PlainEntityManagerEventArgs(T value) {
            this.value = value;
        }

        public T Value {
            get {
                return value;
            }
        }
    }

    #endregion

    #region PlainEntityManager

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TT"></typeparam>
    [ParseChildren(true)]
    public class PlainEntityManagerGeneric<T, TT> : Control where T : new() {

        #region Events

        public event EventHandler<PlainEntityManagerEventArgs<T>> BeforeSave;
        public event EventHandler<PlainEntityManagerEventArgs<T>> AfterSave;

        #endregion

        #region Fields

        private T entity;
        private ILog log = LogManager.GetLogger("Plain.Web.PlainEntityManager");
        private IFormBinding FormBinder = PlainWebApplication.Config.FormBinder;
        private String saveControlId = null;
        private ParameterCollection parameterId;

        #endregion

        #region Properties

        /// <summary>
        /// Returns the Id the page is loaded with. Throws an Exception if id is not right type.
        /// </summary>
        public virtual TT EntityId {
            get {
                log.Debug("Retrieving EntityId");
                if (parameterId[0] is FormParameter) {
                    FormParameter fp = parameterId[0] as FormParameter;
                    string val = this.Page.Request.Form[fp.FormField];
                    if (!String.IsNullOrEmpty(val)) {
                        return (TT)Convert.ChangeType(val, typeof(TT), null);
                    } else if (!String.IsNullOrEmpty(fp.DefaultValue)) {
                        return (TT)Convert.ChangeType(fp.DefaultValue, typeof(TT), null);
                    } else {
                        return default(TT);
                    }
                } else if (parameterId[0] is QueryStringParameter) {
                    QueryStringParameter qp = (QueryStringParameter)parameterId[0];
                    string val = this.Page.Request[qp.QueryStringField];
                    if (!String.IsNullOrEmpty(val)) {
                        return (TT)Convert.ChangeType(val, typeof(TT), null);
                    } else if (!String.IsNullOrEmpty(qp.DefaultValue)) {
                        return (TT)Convert.ChangeType(qp.DefaultValue, typeof(TT), null);
                    } else {
                        return default(TT);
                    }
                } else if (parameterId[0] is ControlParameter) {
                    ControlParameter cp = (ControlParameter)parameterId[0];
                    Control c = this.Page.findControl< Control>().First(x => x.ID == cp.ControlID);
                    object val = c.GetType().GetProperty(cp.PropertyName).GetValue(c, null);
                    if (val != null) {
                        return (TT)Convert.ChangeType(val, typeof(TT), null);
                    } else if (cp.DefaultValue != null) {
                        return (TT)Convert.ChangeType(cp.DefaultValue, typeof(TT), null);
                    } else {
                        return default(TT);
                    }

                } else if (parameterId[0] is CookieParameter) {
                    CookieParameter cp = (CookieParameter)parameterId[0];
                    HttpCookie cookie = this.Page.Request.Cookies[cp.CookieName];
                    if (cookie != null && !String.IsNullOrEmpty(cookie.Value)) {
                        return (TT)Convert.ChangeType(cookie.Value, typeof(TT), null);
                    } else if (!String.IsNullOrEmpty(cp.DefaultValue)) {
                        return (TT)Convert.ChangeType(cp.DefaultValue, typeof(TT), null);
                    } else {
                        return default(TT);
                    }
                } else if (parameterId[0] is SessionParameter) {
                    SessionParameter sp = (SessionParameter)parameterId[0];
                    object val = HttpContext.Current.Session[sp.SessionField];
                    if (val != null) {
                        return (TT)Convert.ChangeType(val, typeof(TT), null);
                    } else if (!String.IsNullOrEmpty(sp.DefaultValue)) {
                        return (TT)Convert.ChangeType(sp.DefaultValue, typeof(TT), null);
                    } else {
                        return default(TT);
                    }
                } else if (parameterId[0] is ProfileParameter) {
                    ProfileParameter pp = (ProfileParameter)parameterId[0];
                    object val = HttpContext.Current.Profile[pp.PropertyName];
                    if (val != null) {
                        return (TT)Convert.ChangeType(val, typeof(TT), null);
                    } else if (!String.IsNullOrEmpty(pp.DefaultValue)) {
                        return (TT)Convert.ChangeType(pp.DefaultValue, typeof(TT), null);
                    } else {
                        return default(TT);
                    }
                } else {
                    return default(TT);
                }
            }
        }

        /// <summary>
        /// Get the entity of type T
        /// </summary>
        public T Entity {
            get {
                if (entity == null) {
                    log.Debug("Loading entity");
                    MethodInfo dmGetEntity = DaoReflector.GetDaoMethod("Get", DaoFactory, typeof(T), typeof(TT));
                    entity = (T)dmGetEntity.Invoke(DaoReflector.GetDao(DaoFactory, typeof(T), typeof(TT)), new Object[] { EntityId });
                    if (entity == null)
                        entity = new T();
                }
                return entity;
            }
        }

        /// <summary>
        /// Returns the <see cref="IDaoFactory"/> used by the <see cref="Dao"/> property to get Dao objects
        /// </summary>
        protected virtual IDaoFactory DaoFactory {
            get { return PlainWebApplication.Config.DaoFactory; }
        }

        /// <summary>
        /// Indicates if the page has changed
        /// </summary>
        public bool IsChanged {
            get {
                string checksumLoad = (string)ViewState["checksumLoad"];
                string newChecksum = ClientComparer.CalculateChecksum(this);
                if (newChecksum != checksumLoad) {
                    return true;
                } else {
                    return false;
                }
            }
        }

        /// <summary>
        /// Get or set what event the <c href="PlainEntityManager">PlainEntityManager</c> bind its entities at
        /// </summary>
        public PageEvents BindEvent {
            get;
            set;
        }

        /// <summary>
        /// Sets the control with the specified Id that triggers the save event.
        /// </summary>
        public string SaveButtonId {
            set {
                saveControlId = value;
            }
        }


        /// <summary>
        /// Specifies how to get the id to fetch the entity with.
        /// Only 1 parameter can be specified
        /// </summary>
        [Browsable(true)]
        [PersistenceModeAttribute(PersistenceMode.InnerProperty)]
        public ParameterCollection ParameterId {
            get {
                return parameterId;
            }
            set {
                parameterId = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Can find a control with the specified name.
        /// </summary>
        /// <param name="controlName">Id of the control</param>
        /// <returns>The control</returns>
        private Control FindRecursiveControl(string controlName) {
            foreach (Control con in this.Page.Form.Controls) {
                if (con.ID == controlName) {
                    return con;
                }
                if (con.Controls.Count > 0) {
                    Control c = FindRecursiveControl(con.Controls, controlName);
                    if (c != null) {
                        return c;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Can find a control in the given <a href="ControlCollection">ControlCollection</a> with the specified name.
        /// </summary>
        /// <param name="controls">Control collection</param>
        /// <param name="controlName">Id of the control</param>
        /// <returns></returns>
        private Control FindRecursiveControl(ControlCollection controls, string controlName) {
            foreach (Control con in controls) {
                if (con.ID == controlName) {
                    return con;
                }
                if (con.Controls.Count > 0) {
                    Control c = FindRecursiveControl(con.Controls, controlName);
                    if (c != null) {
                        return c;
                    }
                }
            }
            return null;
        }

        #endregion

        #region Eventhandlers

        /// <summary>
        /// Performs the initial bind, that binds the entity to the UI, and wires up the save event
        /// if a saveButtonId is specified
        /// </summary>
        /// <param name="sender">The control that trigged the event</param>
        /// <param name="e">The eventargs</param>
        protected void OnBind(object sender, EventArgs e) {
            if (!this.Page.IsPostBack) {
                log.Debug("Performing BindObjectToPage");
                FormBinder.BindObjectToPage(Entity, this.Page);
            }
            if (!string.IsNullOrEmpty(saveControlId)) {
                Control Save = (Control)FindRecursiveControl(saveControlId);
                if (Save != null) {
                    log.Debug("Save button found. Hooking it up");
                    if (Save is Button)
                        ((Button)Save).Click += new EventHandler(DoSave);
                    else if (Save is LinkButton)
                        ((LinkButton)Save).Click += new EventHandler(DoSave);
                    else if (Save is ImageButton)
                        ((ImageButton)Save).Click += new ImageClickEventHandler(DoSave);
                }
            }
        }

        /// <summary>
        /// Performs a full save circle. If you want to hook into specific part of the logic, implement <see cref="OnBeforeSave"/> or <see cref="OnAfterSave"/>
        /// </summary>
        /// <param name="sender">The control that trigged the event</param>
        /// <param name="e">The eventargs</param>
        public void DoSave(object sender, EventArgs e) {
            log.Debug("Performing BindPageToObject");
            FormBinder.BindPageToObject(Entity, this.Page);

            if (BeforeSave != null)
                BeforeSave(this, new PlainEntityManagerEventArgs<T>(entity));

            log.Debug("Saving Entity");
            MethodInfo dmSaveEntity = DaoReflector.GetDaoMethod("Save", DaoFactory, typeof(T), typeof(TT));
            dmSaveEntity.Invoke(DaoReflector.GetDao(DaoFactory, typeof(T), typeof(TT)), new Object[] { Entity });

            if (AfterSave != null)
                AfterSave(this, new PlainEntityManagerEventArgs<T>(entity));

            if (!((IBusinessEntity<TT>)Entity).Id.Equals(EntityId)) {
                this.Page.Response.Redirect(this.Page.Request.RawUrl + "?id=" + ((IBusinessEntity<TT>)entity).Id);
            }
        }

        #endregion

        #region Overrides

        protected override void OnInit(EventArgs e) {
            log.Debug("Hooking up Event on: " + BindEvent.ToString());
            switch (BindEvent) {
                case PageEvents.OnLoad:
                    this.Page.Load += new EventHandler(OnBind);
                    break;
                case PageEvents.OnPreLoad:
                    this.Page.PreLoad += new EventHandler(OnBind);
                    break;
                case PageEvents.OnLoadComplete:
                    this.Page.LoadComplete += new EventHandler(OnBind);
                    break;
            }
            base.OnInit(e);
        }

        #endregion
    }

    /// <summary>
    /// 
    /// </summary>
    [ControlBuilder(typeof(PlainEntityManageControlBuilder))]
    public class PlainEntityManager : PlainEntityManagerGeneric<Object, Object> {
        private string type;

        public string Type {
            get {
                if (type == null) {
                    return String.Empty;
                }
                return type;
            }
            set {
                type = value;
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class PlainEntityManageControlBuilder : ControlBuilder {
        public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type, string tagName, string id, IDictionary attribs) {
            Type newType = type;
            if (attribs.Contains("type")) {
                Type genericType = typeof(PlainEntityManagerGeneric<,>);
                Type genericArg1 = Type.GetType((string)attribs["type"], true, true);
                Type genericArg2 = genericArg1.GetProperty("Id").PropertyType;
                newType = genericType.MakeGenericType(genericArg1, genericArg2);
                attribs.Remove("type");
            }
            base.Init(parser, parentBuilder, newType, tagName, id, attribs);
        }
    }

    #endregion

    #region PlainEntity Helpers

    public enum PageEvents {
        OnLoad = 0,
        OnPreLoad = 1,
        OnLoadComplete = 2
    }

    #endregion
}